package com.adopapa.ethexplorer.service;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.EthBlock.Block;
import org.web3j.protocol.core.methods.response.EthBlockNumber;
import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.core.methods.response.EthTransaction;
import org.web3j.protocol.core.methods.response.Transaction;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.core.methods.response.admin.AdminNodeInfo;
import org.web3j.protocol.core.methods.response.admin.AdminNodeInfo.NodeInfo;
import org.web3j.protocol.core.methods.response.admin.AdminPeers;
import org.web3j.protocol.core.methods.response.admin.AdminPeers.Peer;
import org.web3j.tx.gas.DefaultGasProvider;
import org.web3j.utils.Numeric;

import com.adopapa.ethexplorer.utils.Tools;

import io.reactivex.Flowable;

/**
 * 与链相关操作
 */
@Service
public class ChaindataService extends BaseService {

	@Autowired
	private Web3j web3j;

	public NodeInfo getAdminNodeInfo() {
		try {
			AdminNodeInfo adminNodeInfo = web3j.adminNodeInfo().send();
			return adminNodeInfo.getResult();
			
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	public List<Peer> getAdminPeers() {
		try {
			AdminPeers adminPeers = web3j.adminPeers().send();
			return adminPeers.getResult();
			
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	public BigInteger getBlockNumber() {
		try {
			EthBlockNumber ehBlockNumber = web3j.ethBlockNumber().send();
			return ehBlockNumber.getBlockNumber();
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	public Block getBlock(long blockNumber, boolean fullTx) {
		try {
			EthBlock ethBlock = web3j.ethGetBlockByNumber(//
					DefaultBlockParameter.valueOf(BigInteger.valueOf(blockNumber)), fullTx).send();
			return ethBlock.getResult();
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	public Block getBlock(String hash, boolean fullTx) {
		try {
			EthBlock ethBlock = web3j.ethGetBlockByHash(hash, fullTx).send();
			return ethBlock.getResult();
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	public List<Block> getBlocks(BigInteger startBlockNumber, BigInteger endBlockNumber, boolean fullTx) {
		DefaultBlockParameter startBlockParameter = DefaultBlockParameter.valueOf(startBlockNumber);
		DefaultBlockParameter endBlockParameter = DefaultBlockParameter.valueOf(endBlockNumber);

		List<Block> blocks = new LinkedList<Block>();
		Flowable<EthBlock> flowable = web3j.replayPastBlocksFlowable(startBlockParameter, endBlockParameter, fullTx);

		flowable.blockingForEach(block -> {
			blocks.add(block.getResult());
		});

		return blocks;
	}

	public Page<Block> getBlockPage(int pageNo, int pageSize) {
		// query block number
		BigInteger blockNumber = getBlockNumber();

		// compute start and end
		BigInteger start = blockNumber.subtract(BigInteger.valueOf((pageNo - 1) * pageSize));
		BigInteger end = blockNumber.subtract(BigInteger.valueOf(pageNo * pageSize - 1));
		if (end.compareTo(BigInteger.ONE) < 0) {
			end = BigInteger.ONE;
		}

		List<Block> blocks = getBlocks(end, start, false);
		Collections.reverse(blocks);

		PageRequest pageRequest = PageRequest.of(pageNo, pageSize);
		Page<EthBlock.Block> page = new PageImpl<EthBlock.Block>(blocks, pageRequest, blockNumber.longValue());

		return page;
	}

	public Transaction getTransaction(String txHash) {
		try {
			EthTransaction transaction = web3j.ethGetTransactionByHash(txHash).send();
			return transaction.getResult();
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	///////////////////////////////
	public List<String> getPendingTransactionHash() {
		List<String> hashes = new LinkedList<String>();
		Flowable<String> flowable = web3j.ethPendingTransactionHashFlowable();
		flowable.blockingForEach(hash -> {
			hashes.add(hash);
		});
		return hashes;
	}

	public BigInteger getNonce(String address) {
		try {
			EthGetTransactionCount ethGetTransactionCount = //
					web3j.ethGetTransactionCount(address, DefaultBlockParameterName.PENDING).send();
			return ethGetTransactionCount.getTransactionCount();
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	public BigInteger getBalance(String address) {
		try {
			EthGetBalance ethGetBalance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
			return ethGetBalance.getBalance();
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	/**
	 * 发送交易返回hash
	 * 
	 * @param signedData
	 */
	public String sendTransaction(String signedData) {
		try {
			EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(signedData).send();
			return ethSendTransaction.getTransactionHash();
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}

	}

	public TransactionReceipt getTransactionReceipt(String txHash) {
		try {
			EthGetTransactionReceipt ethGetTransactionReceipt = web3j.ethGetTransactionReceipt(txHash).send();
			return ethGetTransactionReceipt.getResult();
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	/**
	 * 部署智能合约
	 */
	public String deployContract(Credentials credentials, String binary) {
		// BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, BigInteger value,
		// String init
		DefaultGasProvider defaultGasProvider = new DefaultGasProvider();
		RawTransaction rawTransaction = //
				RawTransaction.createContractTransaction(getNonce(credentials.getAddress()), //
						defaultGasProvider.getGasPrice(), //
						defaultGasProvider.getGasLimit(), //
						BigInteger.ONE, binary);
		byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
		String signedData = Numeric.toHexString(signedMessage);

		String txHash = this.sendTransaction(signedData);
		System.out.println(txHash);
		if (Tools.empty(txHash)) {
			throw new RuntimeException("invalid sender");
		}
//		TransactionReceipt transactionReceipt = getTransactionReceipt(txHash);
		return txHash;
	}

	public String transfer(Credentials credentials, String to, BigInteger value, String data) {
		DefaultGasProvider defaultGasProvider = new DefaultGasProvider();
		// BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String to, String
		// data
		RawTransaction rawTransaction = //
				RawTransaction.createTransaction(getNonce(credentials.getAddress()), //
						defaultGasProvider.getGasPrice(), //
						defaultGasProvider.getGasLimit(), //
						to, value, data);
		byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
		String signedData = Numeric.toHexString(signedMessage);

		String txHash = this.sendTransaction(signedData);
		System.out.println(txHash);

//		web3j.adminNodeInfo();
//		web3j.adminPeers();

		return txHash;
	}
}
